#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>     /*包含close函数，关闭套接字文件描述符*/
#include "chat.h"


#define SENDBUFFERSIZE  256

int login_flag = -1;


/**
 * @brief         满足:1.向服务器发送信息。
 *                     2.同时接收服务器信息
 * 
 * @attention
 */



int regist(int sockfd)
{
    struct MyProtocol msg, msg_back;
        
    msg.cmd = REGISTE;
    printf("input your name:\n");
    scanf("%s", msg.name);
    printf("input your password:\n");
    scanf("%s", msg.data);

    send(sockfd, &msg, sizeof(struct MyProtocol), 0);

    recv(sockfd, &msg_back, sizeof(struct MyProtocol), 0);
    if(msg_back.state == OP_OK)
    {
        printf("register success!\n");
        getchar();
        getchar();
        return 0;
    }
    else
    {
        printf("name had exist, try again!\n");
        getchar();
        getchar();
        return -1;
    }
}

int login(int sockfd)
{
    struct MyProtocol msg, msg_back;
    msg.cmd = LOGIN;
    printf("input your name:\n");
    scanf("%s", msg.name);
    printf("input your password:\n");
    scanf("%s", msg.data);

    send(sockfd, &msg, sizeof(struct MyProtocol), 0);

    recv(sockfd, &msg_back, sizeof(struct MyProtocol), 0);
    if(msg_back.state == OP_OK)
    {
        printf("login success!\n");
        getchar();
        getchar();
        login_flag = 1;
        return OP_OK;
    }
    else if(msg_back.state == USER_LOGED)
    {
        printf("have been login!\n");
        getchar();
        getchar();
        login_flag = -1;
        return USER_LOGED;
    }
    else
    {
        printf("password error, try again!\n");
        getchar();
        getchar();
        login_flag = -1;
        return NAME_PWD_NMATCH;
    }

}

void broadcast(int sockfd)
{
    struct MyProtocol msg;
    msg.cmd = BROADCAST;
    printf("say : \n#");
    scanf("%s", msg.data);
    write(sockfd, &msg, sizeof(msg));

}

void private(int sockfd)
{
    struct MyProtocol msg;
    msg.cmd = PRIVATE;
    printf("input name you want to talk:\n#");
    scanf("%s", msg.name);
    
    printf("say : \n#");
    scanf("%s", msg.data);
    write(sockfd, &msg, sizeof(msg));
}

void list_online_user(int sockfd)
{
    struct MyProtocol msg;
    msg.cmd = ONLINEUSER;
    write(sockfd, &msg, sizeof(msg));
    getchar();
    getchar();
}

void logout(int sockfd)
{
    close(sockfd);
    login_flag = -1;
}




/**
 * @brief         子线程用于接收服务器端的数据，打印到终端
 * 
 * @param         arg: socket file descriptor
 * @return        void*
 * @attention
 */
void *client_recv_fun(void *arg)
{
    int sockfd = *((int *)arg);

    char buffer[SENDBUFFERSIZE] = {0};
    int nbytes;

    struct MyProtocol *msg;


    /*循环接收服务端发送来的数据，打印在终端上*/
    while(1)
    {
        if(login_flag != 1)
        {
            continue;
        }
        memset(buffer, 0, sizeof(buffer));

        nbytes = recv(sockfd, buffer, SENDBUFFERSIZE, 0);
        if(nbytes == -1)
        {
            printf("[system:recv error!]\n");
            continue;
        }
        if(nbytes == 0)
        {
            printf("socket disconnect,please connect again!\n");
            close(sockfd);
            exit(-1);
        }
        msg = (struct MyProtocol *)buffer;
        if((msg->state == ONLINEUSER_OK) &&(msg->cmd == ONLINEUSER))
        {
            printf("%s\t", msg->name);
            continue;
        }
        if((msg->state == ONLINEUSER_OVER) &&(msg->cmd == ONLINEUSER))
        {
            printf("\n");
            continue;
        }

        buffer[nbytes] = '\0';
        printf("server: %s\n", buffer);
    }

    return (void *)0;

}


int main(int argc, char **argv)
{

    struct sockaddr_in server_addr;     /*struct sockaddr_in是在头文件<arpa/inet.h>中定义*/

    int sockfd;
    int ret;
    unsigned int sin_size;
    pthread_t tid;
    int sel, min_sel, max_sel;
    

    if(argc != 3)
    {
        printf("Usage: %s <ip:x.x.x.x> <port>\n", argv[0]);
        return -1;
    }
    
    /*构建服务器地址结构体
     *填充服务器端sockaddr结构
     */
    bzero(&server_addr, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    /*ip地址转换*/
    ret = inet_aton(argv[1], &server_addr.sin_addr);
    if(ret != 1)
    {
        printf("Usage: %s <ip:x.x.x.x> <port>\n", argv[0]);
        return -1;
    }
    server_addr.sin_port = htons(atoi(argv[2]));

    /*1.创建套接字*/
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1)
    {
        printf("socket establish error\n");
        return -1;
    }

    /*2.连接服务器*/
    sin_size = sizeof(struct sockaddr);
    ret = connect(sockfd, (struct sockaddr *)(&server_addr), sin_size);
    if(ret == -1)
    {
        close(sockfd);
        printf("connect: %s:%s error\n", argv[1], argv[2]);
        return -1;
    }

    /*3.服务器成功建立连接
     *3.1主线程用于接收客户端终端输入字符，并发送给服务器端。
     *3.2子线程用于接收服务器端数据，打印在终端。
     */

    /*3.2子线程用于接收服务器端数据，打印在终端。*/
    pthread_create(&tid, NULL, client_recv_fun, &sockfd);

    /*3.1主线程用于接收客户端的输入，处理相应的逻辑。*/
    system("clear");
    printf("===== WELCOME =====\n");
    while(1)
    {
        if(login_flag == -1)
        {
            printf("\t1 注册\n");
            printf("\t2 登录\n");
            min_sel = 1;
            max_sel = 2;
        }
        else if(login_flag == 1)
        {
            printf("\t3 公聊\n");
            printf("\t4 私聊\n");
            printf("\t5 在线列表\n");
            min_sel = 3;
            max_sel = 5;
        }
        printf("\t0 退出\n");

        fflush(stdin);
        scanf("%d", &sel);
        if(sel == 0)
        {
            break;
        }
        if(sel < min_sel || sel > max_sel)
        {
            printf("Invalid choice, try again!\n");
            continue;
        }

        switch (sel) 
        {
            case 1:
                regist(sockfd);
                break;
            case 2:
                ret = login(sockfd);
                break;
            case 3:
                broadcast(sockfd);
                break;
            case 4:
                private(sockfd);
                break;
            case 5:
                list_online_user(sockfd);
                break;
            case 0:
                logout(sockfd);
                break;
            default:
                break;
        }

        
    }

    close(sockfd);
    return 0;

}


